#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time   : 2021/7/9 22:32
# @Author : cjw
import copy
from typing import Optional
from datetime import datetime, timedelta

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr

from passlib.context import CryptContext
from jose import JWTError, jwt

app06 = APIRouter()

"""密码模式和FastAPI的OAuth2PasswordBearer"""
"""
1、OAuth2PasswordBearer是接收URL作为参数的一个类，客户端会向该URL发送username和password参数，然后得到一个Token值
2、OAuth2PasswordBearer并不会创建相应的URL路径操作，只是指明客户端用来请求Token的URL地址
3、当请求到来的时候，FastAPI会检查请求的Authorization头信息，如果没有找到Authorization头信息，或者头信息的内容不是Bearer token,
它会返回401状态码(UNAUTHORIZED)
"""
# 请求Token的URL地址：http://127.0.0.1:8000/chapter06/token
oauth2_schema = OAuth2PasswordBearer(tokenUrl='/chapter06/token')


@app06.get('/oauth2_password_bearer', summary='获取token')
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
    return {'token': token}


"""基于password和Bearer token的OAuth2认证"""
fake_users_db = {
    "john": {
        "username": "john",
        "full_name": "John Doe",
        "email": "john@example.com",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "alice@example.com",
        "hashed_password": "fakehashedsecret2",
        "disabled": True,
    },
}


def fake_hash_password(password: str):
    """
    模拟加密
    :param password:
    :return:
    """
    return f'fakehashed{password}'


class User(BaseModel):
    username: str
    email: Optional[EmailStr] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None  # 是否活跃用户


class UserInDB(User):
    hashed_password: str


async def get_user(db, username: str):
    """获取用户"""
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


async def fake_decode_token(token: str):
    """解密token信息"""
    user = await get_user(fake_users_db, token)
    return user


async def get_current_user(token: str = Depends(oauth2_schema)):
    """获取当前用户"""
    user = await fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail='Invalid authentication credentials',
            # OAuth2的规范，如果认证失败，请求头中返回“WWW-Authenticate”
            headers={'WWW-Authenticate': 'Bearer'}
        )
    return user


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    """获取当前活跃的用户"""
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Inactive user')
    return current_user


@app06.post('/token', summary='登录获取token')
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    """登录获取token"""
    # 获取用户数据，验证用户名
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
                            detail='Incorrect username')
    user = UserInDB(**user_dict)

    # 验证用户密码
    hashed_password = fake_hash_password(form_data.password)  # hash加密
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
                            detail='Incorrect password')
    return {'access_token': user.username, 'token_type': 'bearer'}


@app06.get('/users/me', summary='获取当前活跃用户')
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


"""开发基于JSON Web Tokens的认证"""
# 生成加密钥，linux/mac系统中运行:
# openssl rand -hex 32
SECRET_KEY = "a062f3515f88618e1fc70f16fdcc5623cf0384c5b5913c2a9c5ff2d3b505ae6ecf9bbc6f00b04e207710d5fb7f" \
             "aeeb752872c43ab2fc36aa8cacd2c263c54554"
ALGORITHM = "HS256"  # 加密算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 访问令牌过期时间，单位：分钟

fake_users_db.update({
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": "$2b$12$j.c/RtlZiPkNelczJoKve.mIO8xUizjiZzzemPxjfsdBF18zc5f1m",
        "disabled": False,
    }
})


class Token(BaseModel):
    """返回给用户的Token格式"""
    access_token: str
    token_type: str


class TokenData(BaseModel):
    username: Optional[str] = None


# 密码加密方式
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
# 创建token
jwt_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/chapter06/jwt/token")


def verify_password(plain_password: str, hashed_password: str):
    """
    密码校验
    :param plain_password: 明文密码
    :param hashed_password: 哈希加密密码
    :return:
    """
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str):
    """
    明文密码哈希加密
    :param password: 明文密码
    :return:
    """
    return pwd_context.hash(password)


def jwt_get_user(db, username: str):
    """
    获取用户
    :param db: 数据库查询返回的结果数据
    :param username: 用户名
    :return:
    """
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


def jwt_authenticate_user(fake_db, username: str, password: str):
    """
    验证用户
    :param fake_db: 数据库查询返回的结果数据
    :param username: 用户名
    :param password: 密码
    :return:
    """
    user = jwt_get_user(fake_db, username)  # 获取用户
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """
    创建token
    :param data: token需要加密的数据
    :param expires_delta: token过期时间
    :return:
    """
    to_encode = copy.deepcopy(data)  # 深拷贝

    # 过期时间处理
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({'exp': expire})

    # 创建token,jwt令牌
    encode_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encode_jwt


async def get_jwt_current_user(token: str = Depends(jwt_oauth2_scheme)):
    """
    获取当前用户
    :param token:
    :return:
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, ALGORITHM)  # token解密
        username: str = payload.get('sub')  # 获取解密token的包含的数据
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = jwt_get_user(fake_users_db, username=token_data.username)  # 获取用户名
    if user is None:
        raise credentials_exception
    return user


async def get_jwt_current_active_user(current_user: User = Depends(get_jwt_current_user)):
    """
    获取当前活跃用户
    :param current_user: 当前活跃用户
    :return:
    """
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
    return current_user


@app06.post('/jwt/token', response_model=Token, summary='jwt获取token')
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = jwt_authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码不正确",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)  # token过期时间
    access_token = create_access_token(
        data={'sub': user.username},
        expires_delta=access_token_expires)
    return {"access_token": access_token, "token_type": "bearer"}


@app06.get("/jwt/users/me/", response_model=User, summary='jwt获取当前用户')
async def read_users_me(current_user: User = Depends(get_jwt_current_user)):
    return current_user


@app06.get("/users/me/items/", summary='jwt获取当前活跃用户')
async def read_own_items(current_user: User = Depends(get_jwt_current_active_user)):
    return [{"item_id": "Foo", "owner": current_user.username}]
